'''
js逆向案例
    1.一个平台的加密机制或者解密机制是一样的
'''

'''
1.全国建筑市场（存在响应数据加密）
网站：https://jzsc.mohurd.gov.cn/data/company
思路分析：
    XHR断点：
        XHR/提取断点输入响应包的地址（去除参数）
        刷新进入断点调试
        点击跳过下一个函数调用至this出现开头类似的特殊密文字符串i，并且源码处o存在字典的形式
        执行下一步，传值i赋予给o字典里的data，可以找到response里面密文与响应数据包密文一致
        观察o字典data值传递到哪里进行一系列解析：
            发现传递到r里面，鼠标指向r可以进入r的函数里面
            发现o对应的是r里面的n，观察n，发现n使用了某个方法（功能不明只能跳过）
            出来以后进入新的函数变成变量t代替
        执行下一步至发现t使用data值传进h方法中，并再次传入JSON.parse方法里面
            JSON.parse是将数据转换成json数据，目的将数据转换成语言自身能操作的数据类型
            说明h方法出来以后已经完成解密，所以可以锁定h方法就是所需要的
        进入h方法中，可以看到有一套AES加密规则：
            传值t经过Hex解密再经过Base64解密，最后AES
             , f = d.a.enc.Utf8.parse("jo8j9wGw%6HbxfFn")
             , m = d.a.enc.Utf8.parse("0123456789ABCDEF");
            function h(t) {
            var e = d.a.enc.Hex.parse(t)
              , n = d.a.enc.Base64.stringify(e)
              # f是密钥，n是进过Base64解密的数据（要用AES需要将数据通过Base64解密后才能执行）
              , a = d.a.AES.decrypt(n, f, {
                # iv偏移量：m（需要寻找上文）
                iv: m,
                # AES加密模式：CBC
                mode: d.a.mode.CBC,
                # 填充：Pkcs7
                padding: d.a.pad.Pkcs7
            }

    去除参数的url:
        'https://jzsc.mohurd.gov.cn/api/webApi/dataservice/query/comp/list'
    响应数据包密文字符串（很长）：
        95780ba0943730051dccb5fe3918f9fe1b6f2130681f99d5620c5497aa480f132c671522d91ab2a2dcc1bad4d....f228
    第一次加密处出现的密文字符串i（短的）：
        95780ba0943730051dccb5fe3918f9fe1b6f2130681f9d43513d05ff03d9698258e442523
    第二次加密处出现的密文字符串i（很长）：
        95780ba0943730051dccb5fe3918f9fe1b6f2130681f99d5620c5497aa480f132c671522d91ab2a2dcc1bad4d....f228

    密钥：jo8j9wGw%6HbxfFn
    iv偏移量：0123456789ABCDEF
    加密模式:CBC
    填充：Pkcs7
    输出：Hex

    第三方原生解密模块：Crypto
        需要安装pip install pycryptodome
        from Crypto.Cipher import AES
'''
# 使用第三方平台解密
from requests_html import HTMLSession
from urllib.parse import quote
# 导入json模块提取数据
import json
import time

# 创建请求对象
session = HTMLSession()


class JZSpider(object):

    def __init__(self):
        # 用户输入
        self.user_input = input('请输入你需要搜索的地方：')
        # 请求地址(需要翻页和搜索名字，删除最后的参数仍然能获取相同密文)
        self.start_url = 'https://jzsc.mohurd.gov.cn/APi/webApi/dataservice/query/comp/list?complexname={}&pg={}&pgsz=15'
        # 请求头(无痕浏览仍可获得响应数据，只需要ua)
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36',
        }
        # 第三方解密平台url和headers
        self.enc_url = 'https://tool.lmeee.com/jiami/crypt128inter'
        self.enc_headers = {
            'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
            'cookie': 'LMSID=c73lt8bu6nr4p2k05kpfi8j8ah; Hm_lvt_d2e38a1fa20e8875ffb8a503a92e649d=1679051290,1679260184,1679304385; Hm_lpvt_d2e38a1fa20e8875ffb8a503a92e649d=1679304388',
            'origin': 'https://tool.lmeee.com',
            'referer': 'https://tool.lmeee.com/jiami/aes',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36',
            'x-requested-with': 'XMLHttpRequest',
        }

    def parse_start_url(self):
        # 模拟翻页
        for page in range(5):
            # 将用户输入进行转码
            quote_str = quote(self.user_input)
            # 完成地址拼接
            url = self.start_url.format(quote_str, page)
            # 发送请求，获取响应（使用content.decode（）获取密文字符串）
            response = session.get(url, headers=self.headers).content.decode()
            # print(response)
            start = time.time()
            print(f'——————正在采集全国建筑市场{self.user_input}地区第{page + 1}页数据——————')
            self.parse_response_data(response)
            end = time.time() - start
            print(f'——————全国建筑市场{self.user_input}地区第{page + 1}页数据采集完成，用时{"%.2f" % end}秒——————')
            # break

    def parse_response_data(self, response):
        # 构造第三方解密网站的params请求参数
        params = {
            'mode': 'CBC',
            'padding': 'pkcs7',
            'block': '128',
            'password': 'jo8j9wGw%6HbxfFn',
            'iv': '0123456789ABCDEF',
            'encode': 'hex',
            'way': '2',
            'text': f'{response}',
            'method': 'aes',
        }
        # 对接第三方解析平台解析密文字符串(注意发送频率，有些解密平台有限制)
        # params和data的用法
        # 1、get请求参数在url里面是能够看见的，必须使用params
        # 2、post请求既可以使用params，也可以使用data
        # 总结：能看见的参数，传参就用params，看不见的参数，就用data
        response2 = session.post(self.enc_url, headers=self.enc_headers, data=params).json()
        # print(response2)
        # 使用json.loads提取数据，将json格式数据转换为字典
        json_data_list = json.loads(response2['d']['r'])['data']['list']
        print(json_data_list, '\n\n\n')
        for data in json_data_list:
            # print(data)
            # 提取id
            qy_id = data['QY_ID']
            # 提取统一社会信用代码
            code = data['QY_ORG_CODE']
            # 提取企业名称
            qy_name = data['QY_NAME']
            # 提取企业法定代表人
            fr_name = data['QY_FR_NAME']
            # 提取注册属地
            region = data['QY_REGION_NAME']

            # 构造数据对象整理数据
            item = {
                'id': qy_id,
                '统一社会信用代码': code,
                '企业名称': qy_name,
                '企业法定代表人': fr_name,
                '注册属地': region
            }
            self.parse_save_data(item)

    def parse_save_data(self, item):
        with open('全国建筑市场企业数据采集.txt', 'a', encoding='utf-8') as f:
            f.write(f'{item}')
            f.write('\n')


if __name__ == '__main__':
    s = JZSpider()
    s.parse_start_url()
